namespace Guia.Elements
{
    using System;
    using System.Diagnostics;
    using System.Diagnostics.Contracts;
    using System.Reflection;
    using System.Threading;
    using System.Windows.Automation;
    using System.Windows.Media.Animation;
    using System.Windows.Threading;
    using Guia.Config;
    using Guia.Core;
    using Guia.Locator;

    /// <summary>
    /// This class represents a runnable UiElement.
    /// That is a UiElement which can be started directly without the need to instantiate an application.
    /// </summary>
    public abstract class RunnableUiElement : UiElement, IRunnableEntity
    {
        /// <summary>
        /// Timeout for the closing of the modal windows.
        /// </summary>
        private static readonly int CloseModalWindowsTimeout = AppConfigImpl.Instance.CloseModalWindowsTimeout;
        
        /// <summary>
        /// The underlying thread needed to run the UiElement inside.
        /// </summary>
        private Thread thread;

        /// <summary>
        /// Initializes a new instance of the <see cref="RunnableUiElement"/> class.
        /// </summary>
        /// <param name="automationElement">The AutomationElement representing this UiElement.</param>
        protected RunnableUiElement(AutomationElement automationElement)
            : base(automationElement)
        {
        }

        /// <summary>
        /// Stopps the execution of the runnable UiElement.
        /// </summary>
        public void Stop()
        {
            this.Stop(false);
        }

        /// <summary>
        /// Stopps the execution of the runnable UiElement.
        /// </summary>
        /// <param name="closeModalWindows">
        /// If this argument is true, all modal windows are closed.
        /// </param>
        /// <exception cref="TimeoutException">This exception is thrown if it takes too long to close all modal windows.</exception>
        public void Stop(bool closeModalWindows)
        {
            if (closeModalWindows)
            {
                Action action = () => this.CloseOpenElements();
                Action timeoutAction = () => { throw new TimeoutException(); };
                Core.Clock.Run(action, timeoutAction, CloseModalWindowsTimeout);
            }

            if (this.thread != null)
            {
                try
                {
                    Dispatcher windowDispatcher = Dispatcher.FromThread(this.thread);
                    if (windowDispatcher != null)
                    {
                        windowDispatcher.InvokeShutdown();
                    }
                    else
                    {
                        this.thread.Abort();
                    }

                    this.thread.Join();
                }
                finally
                {
                    // delete the reference
                    this.thread = null;
                }
            }
        }

        /// <summary>
        /// Starts the runnable UiElement of the given type.
        /// </summary>
        /// <typeparam name="T">The conrete RunnableUiElement-type.</typeparam>
        /// <param name="function">This function has to return the window that should be shown</param>
        /// <returns>The started RunnableUiElement</returns>
        /// <exception cref="ArgumentNullException">This exception is thrown if function is null.</exception>
        protected static T Start<T>(Func<System.Windows.Window> function) where T : RunnableUiElement
        {
            Contract.Requires<ArgumentNullException>(function != null);
            T runnableUiElement = null;
            try
            {
                ManualResetEvent windowNameSetEvent = new ManualResetEvent(false);
                string windowName = string.Empty;
                Thread localThread = new Thread(() =>
                {
                    System.Windows.Window window = function.Invoke();
                    try
                    {
                        windowName = window.Title;
                        window.Show();
                        windowNameSetEvent.Set();
                        try
                        {
                            Dispatcher.Run();
                        }
                        catch (AnimationException)
                        {
                            // Not fatal
                        }
                    }
                    catch (ThreadAbortException)
                    {
                        Dispatcher.CurrentDispatcher.InvokeShutdown();
                    }
                });
                localThread.SetApartmentState(ApartmentState.STA);
                localThread.Start();
                windowNameSetEvent.WaitOne();
                UiElement automationWindow = GetWindowByName(windowName);
                automationWindow.SetFocus();
                ConstructorInfo constrInfo = GetConstructorInfo(typeof(T), automationWindow.AutomationElement);
                runnableUiElement = (T)constrInfo.Invoke(new object[] { automationWindow.AutomationElement });
                runnableUiElement.thread = localThread;
            }
            catch
            {
            }

            return runnableUiElement;
        }

        /// <summary>
        /// Gets the CunstructorInfo for a given Type and given constructor parameters
        /// </summary>
        /// <param name="type">
        /// The type. The constructor for this type is searched.
        /// </param>
        /// <param name="constructorParameters">
        /// The constructor parameters.
        /// </param>
        /// <returns>
        /// CunstructorInfo for a given Type and given constructor parameters
        /// </returns>
        /// <exception cref="NoConstructorFoundException">
        /// </exception>
        protected static ConstructorInfo GetConstructorInfo(Type type, params object[] constructorParameters)
        {
            // Create constructor
            ConstructorInfo constructorInfo = null;
            if (constructorParameters.Length >= 1)
            {
                Type[] paramTypes = new Type[constructorParameters.Length];
                for (int i = 0; i < constructorParameters.Length; i++)
                {
                    Type paramType = constructorParameters[i].GetType();
                    paramTypes[i] = paramType;
                }

                constructorInfo = type.GetConstructor(paramTypes);
            }
            else
            {
                constructorInfo = type.GetConstructor(new Type[0]);
            }

            if (constructorInfo == null)
            {
                throw new NoConstructorFoundException("No constructor for the given parameter was found");
            }

            return constructorInfo;
        }

        /// <summary>
        /// This method is executed, before the Runnable entity is being stopped.
        /// It can contain functionality which stopps modal-windows.
        /// </summary>
        protected abstract void CloseOpenElements();

        /// <summary>
        /// Returns the window with the specified Name
        /// </summary>
        /// <param name="name">Name of the window</param>
        /// <returns>Window with the given name</returns>
        private static Window GetWindowByName(string name)
        {
            SearchCondition searchCondition =
                SearchCondition.ByName(name).And(SearchCondition.ByControlType(ControlType.Window));
            return WindowHelper.GetWindowBySearchCondition(searchCondition, Process.GetCurrentProcess().Id);
        }
    }
}